﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using ESRI.ArcGIS.ArcMapUI;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Catalog;
using ESRI.ArcGIS.CatalogUI;
using ESRI.ArcGIS.DataSourcesRaster;
using ESRI.ArcGIS.Framework;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geoprocessor;
using ESRI.ArcGIS.esriSystem;

namespace Bes.Swsp.ToolBox
{
  internal partial class ToolBoxForm : Form
  {
    private bool _cancelPressed = false;
    private ToolBoxSettings _settings;
    private ToolBox _toolbox;

    internal ToolBoxForm(ToolBoxSettings settings)
    {
      InitializeComponent();

      _settings = settings;
    }

    private ToolBox Toolbox
    {
      get
      {
        if (_toolbox == null)
        {
          _toolbox = new ToolBox();
          _toolbox.Progress += new EventHandler<ProgressEventArgs>(_ReportProgress);
        }
        return _toolbox;
      }
    }

    private void FindDownstreamForm_Load(object sender, EventArgs e)
    {
      _SetupDocumentEvents();

      _UpdateComboBoxes();

      _LoadSettings();
      _UpdateStormNetworkLabel();

      loggingCheckBox.Text = string.Format("Log messages to {0}.", Logger.LogFile);
      loggingCheckBox.Checked = Logger.IsLogging;
    }

    protected override void OnClosed(EventArgs e)
    {
      _RemoveDocumentEvents();
      base.OnClosed(e);
    }

    private void _SetupDocumentEvents()
    {
      //Listen to events which would change combobox contents
      IDocumentEvents_Event docEvents = (IDocumentEvents_Event)ArcMap.Document;
      docEvents.ActiveViewChanged += this._OnFocusMapChanged;

      IActiveViewEvents_Event activeViewEvents = (IActiveViewEvents_Event)ArcMap.Document.ActiveView;
      activeViewEvents.FocusMapChanged += this._OnFocusMapChanged;
      activeViewEvents.ItemAdded += this._OnLayersChanged;
      activeViewEvents.ItemDeleted += this._OnLayersChanged;
      activeViewEvents.ItemReordered += this._OnLayersReordered;
    }

    private void _RemoveDocumentEvents()
    {
      IActiveViewEvents_Event activeViewEvents = (IActiveViewEvents_Event)ArcMap.Document.ActiveView;
      activeViewEvents.FocusMapChanged -= this._OnFocusMapChanged;
      activeViewEvents.ItemAdded -= this._OnLayersChanged;
      activeViewEvents.ItemDeleted -= this._OnLayersChanged;
      activeViewEvents.ItemReordered -= this._OnLayersReordered;

      IDocumentEvents_Event pDocEvents = (IDocumentEvents_Event)ArcMap.Document;
      pDocEvents.ActiveViewChanged -= this._OnFocusMapChanged;
    }

    private void _OnFocusMapChanged()
    {
      _UpdateComboBoxes();
    }

    private void _OnLayersChanged(object layer)
    {
      _UpdateComboBoxes();
    }

    private void _OnLayersReordered(object layer, int position)
    {
      _UpdateComboBoxes();
    }

    private void _UpdateComboBoxes()
    {
      List<ComboBoxItem> geometricNetworks = new List<ComboBoxItem>();
      List<ComboBoxItem> polygonLayers = new List<ComboBoxItem>();
      List<ComboBoxItem> lineLayers = new List<ComboBoxItem>();
      List<ComboBoxItem> pointLayers = new List<ComboBoxItem>();
      List<ComboBoxItem> rasterLayers = new List<ComboBoxItem>();

      IMap focusMap = ArcMap.Document.FocusMap;

      int layerCount = focusMap.LayerCount;
      for (int i = 0; i < layerCount; i++)
      {
        ILayer layer = focusMap.get_Layer(i);
        _CheckLayer(layer, geometricNetworks, polygonLayers, lineLayers, pointLayers, rasterLayers);
      }

      _UpdateComboBox(networkComboBox, geometricNetworks);
      _UpdateComboBox(catchmentComboBox, polygonLayers);
      _UpdateComboBox(flowDirComboBox, rasterLayers);
      _UpdateComboBox(runoffComboBox, rasterLayers);
      _UpdateComboBox(nodesComboBox, pointLayers);
      _UpdateComboBox(conveyanceComboBox, lineLayers);
      _UpdateComboBox(accumulationComboBox, rasterLayers);
      _UpdateComboBox(rasterComboBox, rasterLayers);
      _UpdateComboBox(receivingWatersComboBox, polygonLayers);
    }

    private void _CheckLayer(ILayer layer, List<ComboBoxItem> geometricNetworks, List<ComboBoxItem> polygonLayers, List<ComboBoxItem> lineLayers, List<ComboBoxItem> pointLayers, List<ComboBoxItem> rasterLayers)
    {
      bool layerUsed = false;
      if (layer is IGroupLayer && layer is ICompositeLayer)
      {
        ICompositeLayer groupLayer = (ICompositeLayer)layer;
        int layerCount = groupLayer.Count;
        for (int i = 0; i < layerCount; i++)
        {
          ILayer subLayer = groupLayer.get_Layer(i);
          _CheckLayer(subLayer, geometricNetworks, polygonLayers, lineLayers, pointLayers, rasterLayers);
        }
      }
      else if (layer is IFeatureLayer && ((IFeatureLayer)layer).DataSourceType != "SDE Raster Catalog")
      {
        if (((IFeatureLayer)layer).FeatureClass != null)
        {
          if (((IFeatureLayer)layer).FeatureClass is INetworkClass)
          {
            _AddNetworkForLayer(layer, geometricNetworks);

            INetworkClass networkClass = (INetworkClass)((IFeatureLayer)layer).FeatureClass;
          }

          if (((IFeatureLayer)layer).FeatureClass.ShapeType == esriGeometryType.esriGeometryPolygon)
          {
            polygonLayers.Add(new ComboBoxItem() { Name = layer.Name, Value = layer });
            layerUsed = true;
          }

          if (((IFeatureLayer)layer).FeatureClass.ShapeType == esriGeometryType.esriGeometryPolyline)
          {
            lineLayers.Add(new ComboBoxItem() { Name = layer.Name, Value = layer });
            layerUsed = true;
          }

          if (((IFeatureLayer)layer).FeatureClass.ShapeType == esriGeometryType.esriGeometryPoint)
          {
            pointLayers.Add(new ComboBoxItem() { Name = layer.Name, Value = layer });
            layerUsed = true;
          }
        }
      }
      else if (layer is IRasterLayer && ((IRasterLayer)layer).BandCount == 1)
      {
        if (((IRasterLayer)layer).Raster != null)
        {
          rasterLayers.Add(new ComboBoxItem() { Name = layer.Name, Value = layer });
          layerUsed = true;
        }
      }

      if (!layerUsed)
      {
        ComUtility.ReleaseComObject(layer);
      }
    }

    private void _AddNetworkForLayer(ILayer layer, List<ComboBoxItem> geometricNetworks)
    {
      IFeatureLayer featureLayer = (IFeatureLayer)layer;
      INetworkClass networkClass = (INetworkClass)featureLayer.FeatureClass;
      try
      {
        IGeometricNetwork network = networkClass.GeometricNetwork;
        bool alreadyAdded = false;
        foreach (ComboBoxItem element in geometricNetworks)
        {
          if (element.Value == network)
          {
            alreadyAdded = true;
            break;
          }
        }
        if (!alreadyAdded)
        {
          geometricNetworks.Add(new ComboBoxItem() { Name = ((IDataset)network).Name, Value = network });
        }
      }
      finally
      {
        ComUtility.ReleaseComObject(networkClass);
      }
    }

    private void _UpdateComboBox(ComboBox comboBox, List<ComboBoxItem> dataSource, bool includeNone = true)
    {
      if (dataSource != null)
      {
        // We make a copy so that each ComboBox has it's own datasource
        List<ComboBoxItem> dataSourceCopy = new List<ComboBoxItem>(dataSource);
        if (includeNone)
        {
          dataSourceCopy.Insert(0, new ComboBoxItem() { Name = "<None>", Value = null });
        }
        object selectedValue = comboBox.SelectedValue;
        comboBox.DataSource = dataSourceCopy;
        comboBox.DisplayMember = "Name";
        comboBox.ValueMember = "Value";
        if (selectedValue != null)
          comboBox.SelectedValue = selectedValue;
      }
      else
      {
        comboBox.DataSource = null;
        comboBox.Items.Clear();
      }
    }

    private void _LoadSettings()
    {
      if (_settings.PipeNetwork != null)
        networkComboBox.SelectedValue = _settings.PipeNetwork;

      IFeatureLayer catchLayer = _FindFeatureLayer(_settings.CatchmentFeatureClass);
      if (catchLayer != null)
        catchmentComboBox.SelectedValue = catchLayer;

      IRasterLayer flowDirLayer = _FindRasterLayer(_settings.FlowDirectionRaster);
      if (flowDirLayer != null)
        flowDirComboBox.SelectedValue = flowDirLayer;

      IRasterLayer flowAccLayer = _FindRasterLayer(_settings.RunoffRaster);
      if (flowAccLayer != null)
        runoffComboBox.SelectedValue = flowAccLayer;

      IFeatureLayer linksLayer = _FindFeatureLayer(_settings.StormNodesFeatureClass);
      if (linksLayer != null)
        nodesComboBox.SelectedValue = linksLayer;

      IFeatureLayer linesLayer = _FindFeatureLayer(_settings.StormConveyanceFeatureClass);
      if (linesLayer != null)
        conveyanceComboBox.SelectedValue = linesLayer;

      thresholdTextBox.Text = Convert.ToInt32(_settings.FlowAccumulationThreshold).ToString();

      includeTermFlowCheckBox.Checked = _settings.IncludeTerminalFlow;

      IRasterLayer accumulationLayer = _FindRasterLayer(_settings.AccumulationRaster);
      if (accumulationLayer != null)
        accumulationComboBox.SelectedValue = accumulationLayer;
    }

    private void _UpdateSettings()
    {
      _settings.PipeNetwork = networkComboBox.SelectedValue as IGeometricNetwork;

      IFeatureLayer catchLayer = catchmentComboBox.SelectedValue as IFeatureLayer;
      _settings.CatchmentFeatureClass = (catchLayer == null ? null : catchLayer.FeatureClass);

      IRasterLayer flowDirLayer = flowDirComboBox.SelectedValue as IRasterLayer;
      _settings.FlowDirectionRaster = (flowDirLayer == null ? null : flowDirLayer.Raster);

      IRasterLayer runoffLayer = runoffComboBox.SelectedValue as IRasterLayer;
      _settings.RunoffRaster = (runoffLayer == null ? null : runoffLayer.Raster);

      IFeatureLayer linksLayer = nodesComboBox.SelectedValue as IFeatureLayer;
      _settings.StormNodesFeatureClass = (linksLayer == null ? null : linksLayer.FeatureClass);

      IFeatureLayer linesLayer = conveyanceComboBox.SelectedValue as IFeatureLayer;
      _settings.StormConveyanceFeatureClass = (linesLayer == null ? null : linesLayer.FeatureClass);

      _settings.IncludeTerminalFlow = includeTermFlowCheckBox.Checked;

      double threshold;
      if (double.TryParse(thresholdTextBox.Text, out threshold))
        _settings.FlowAccumulationThreshold = threshold;

      IRasterLayer accumulationLayer = accumulationComboBox.SelectedValue as IRasterLayer;
      _settings.AccumulationRaster = (accumulationLayer == null ? null : accumulationLayer.Raster);
    }

    private void _SafeExecute(Action method)
    {
      try
      {
        _UpdateSettings();
        _UpdateToolBox();

        method();
      }
      catch (UserCanceledException)
      {
        _ReportProgress(this, new ProgressEventArgs(1, 1, "User canceled."));
      }
      catch (Exception ex)
      {
        Logger.Log("ToolBoxForm._SafeExecute: " + ex.GetType().FullName + ": " + ex.Message + Environment.NewLine + ex.StackTrace);
        _ShowError(ex);
        _ReportProgress(this, new ProgressEventArgs(0, 1, ex.Message));
      }
    }

    private void createNetworkButton_Click(object sender, EventArgs e)
    {
      _SafeExecute(_CreateNetwork);
    }

    private void _CreateNetwork()
    {
      int threshold;

      if (!int.TryParse(thresholdTextBox.Text, out threshold))
      {
        MessageBox.Show(this, "Unable to parse threshold, please enter an integer.");
        return;
      }

      double conversionFactor = 0.0;
      if (!double.TryParse(networkConversionFactorTextBox.Text, out conversionFactor))
      {
        MessageBox.Show(this, "Please enter a numeric conversion factor", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
      }

      IAoInitialize aoLicense = new AoInitializeClass();
      esriLicenseProductCode product = aoLicense.InitializedProduct();
      if (product != esriLicenseProductCode.esriLicenseProductCodeAdvanced)
      {
        MessageBox.Show(this, "Creating a storm drain network requires an Advanced (ArcInfo) license.", "Insufficient License", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
      }

      if (!aoLicense.IsExtensionCheckedOut(esriLicenseExtensionCode.esriLicenseExtensionCodeSpatialAnalyst))
      {
        MessageBox.Show(this, "Creating a storm drain network requires a Spatial Analyst license.", "Insufficient License", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
      }

      IWorkspace location;
      string name;
      if (_AskUserForOutputFeatureClass("Save storm drainage network nodes feature class.", out location, out name))
      {
        if (location == null)
        {
          MessageBox.Show(this, "Cannot save storm drainage network in this location.", "Invalid Location", MessageBoxButtons.OK, MessageBoxIcon.Warning);
          return;
        }

#warning GxDialog does not ask about overwriting
        //TODO: Warn user about overwriting (need to check existing feature classes, since user enters prefix only)

        StormDrainNetworkLoader loader = _CreateStormNetworkLoader();
        loader.CreateStormNetwork(location, name, threshold, conversionFactor);
        this.Toolbox.StormNetwork = loader.StormNetwork;
        this.Toolbox.StormNetworkNodeClass = loader.StormNetworkNodeClass;
        this.Toolbox.StormNetworkConveyanceClass = loader.StormNetworkConveyanceClass;

        EnumLayer layers = new EnumLayer();
        IFeatureLayer newNodeLayer = null;
        if (loader.StormNetworkNodeClass != null)
        {
          newNodeLayer = new FeatureLayer();
          newNodeLayer.FeatureClass = loader.StormNetworkNodeClass;
          newNodeLayer.Name = loader.StormNetworkNodeClass.AliasName;
          layers.Add(newNodeLayer);
        }
        else
        {
          MessageBox.Show(this, "The storm drainage network could not be generated.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        }

        IFeatureLayer newConveyanceLayer = null;
        if (loader.StormNetworkConveyanceClass != null)
        {
          newConveyanceLayer = new FeatureLayer();
          newConveyanceLayer.FeatureClass = loader.StormNetworkConveyanceClass;
          newConveyanceLayer.Name = loader.StormNetworkConveyanceClass.AliasName;
          layers.Add(newConveyanceLayer);
        }

        ArcMap.Document.FocusMap.AddLayers(layers, true);

        _UpdateComboBoxes();
        nodesComboBox.SelectedValue = newNodeLayer;
        conveyanceComboBox.SelectedValue = newConveyanceLayer;

        _UpdateStormNetworkLabel();
        _UpdateSettings();
      }
    }

    private StormDrainNetworkLoader _CreateStormNetworkLoader()
    {
      StormDrainNetworkLoader loader = new StormDrainNetworkLoader();
      loader.Progress += new EventHandler<ProgressEventArgs>(_ReportProgress);

      loader.PipeNetwork = this.Toolbox.GeometricNetwork;
      loader.CatchmentClass = this.Toolbox.CatchmentClass;
      loader.FlowDirRaster = this.Toolbox.FlowDirRaster;
      loader.RunoffRaster = this.Toolbox.RunoffRaster;
      loader.ExtractTerminalFlow = this.Toolbox.ExtractTerminalFlow;
      loader.StormNetworkConveyanceClass = this.Toolbox.StormNetworkConveyanceClass;
      loader.StormNetworkNodeClass = this.Toolbox.StormNetworkNodeClass;
      loader.StormNetwork = this.Toolbox.StormNetwork;

      return loader;
    }

    private bool _AskUserForOutputFeatureClass(string title, out IWorkspace location, out string name)
    {
      IGxDialog gxDialog = new GxDialogClass();
      gxDialog.ObjectFilter = new GxFilterFeatureClassesClass();
      gxDialog.Title = title;
      if (gxDialog.DoModalSave(ArcMap.Application.hWnd))
      {
        location = ToolBoxExtension.SafeOpen(gxDialog.FinalLocation.InternalObjectName) as IWorkspace;
        name = gxDialog.Name;
        return true;
      }
      else
      {
        location = null;
        name = string.Empty;
        return false;
      }
    }

    private void loadNetworkButton_Click(object sender, EventArgs e)
    {
      _SafeExecute(delegate
      {
        _LoadNetwork(true);
      });
    }

    private void _LoadNetwork(bool showResult)
    {
      _LogLoadNetwork();

      StormDrainNetworkLoader loader = _CreateStormNetworkLoader();
      loader.LoadStormNetwork();
      this.Toolbox.StormNetwork = loader.StormNetwork;

      if (this.Toolbox.StormNetwork != null && showResult)
      {
        MessageBox.Show(this, "Storm Network Loaded.", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
      }

      _UpdateStormNetworkLabel();
    }

    private void _LogLoadNetwork()
    {
      Logger.Log("---------------------------------------");
      Logger.Log(string.Format("Loading Storm Network initiated by {0}", Environment.UserName));
      Logger.Log(string.Format("INPUTS:"));
      Logger.Log(string.Format("Storm Network Nodes: {0}", GeoprocessingUtility.GetFeatureClassPath(this.Toolbox.StormNetworkNodeClass)));
      Logger.Log(string.Format("Storm Network Catchments: {0}", GeoprocessingUtility.GetFeatureClassPath(this.Toolbox.CatchmentClass)));
      Logger.Log(string.Format("Storm Network Conveyances: {0}", GeoprocessingUtility.GetFeatureClassPath(this.Toolbox.StormNetworkConveyanceClass)));
      Logger.Log("---------------------------------------");
    }

    private void checkNetworkButton_Click(object sender, EventArgs e)
    {
      _SafeExecute(_CheckNetwork);
    }

    private void _CheckNetwork()
    {
      this.Toolbox.StormNetwork.DetectErrors();
      _UpdateStormNetworkLabel();
    }

    private void accumulateButton_Click(object sender, EventArgs e)
    {
      _SafeExecute(_AccumulateRaster);
    }

    private void _AccumulateRaster()
    {
      IRasterLayer accumulationLayer = accumulationComboBox.SelectedValue as IRasterLayer;
      if (accumulationLayer == null)
      {
        MessageBox.Show(this, "The local accumulation raster is not valid", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
      }

      double conversionFactor = 0.0;
      if (!double.TryParse(accumulationConversionFactorTextBox.Text, out conversionFactor))
      {
        MessageBox.Show(this, "Please enter a numeric conversion factor", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
      }

      string fieldName = accumulationRasterFieldTextBox.Text;

      IFeatureLayer conveyanceLayer = conveyanceComboBox.SelectedValue as IFeatureLayer;
      IFeatureLayer nodeLayer = nodesComboBox.SelectedValue as IFeatureLayer;
      if (conveyanceLayer == null || nodeLayer == null || conveyanceLayer.FeatureClass == null || nodeLayer.FeatureClass == null)
      {
        MessageBox.Show(this, "The selected storm network layers (nodes and conveyance) are not valid.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
      }

      string validFieldName = FeatureClassUtility.ValidateField(nodeLayer.FeatureClass, fieldName);
      if (nodeLayer.FeatureClass.FindField(validFieldName) != -1 ||
        conveyanceLayer.FeatureClass.FindField(validFieldName) != -1)
      {
        DialogResult response = MessageBox.Show(this, "The storm network already contains output fields for the given accumulation raster. Do you want to overwrite the values in these fields?", "Overwrite values?", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
        if (response == System.Windows.Forms.DialogResult.No)
          return;
      }

      _LogAccumulateStart(accumulationLayer.Raster, conversionFactor);

      if (this.Toolbox.StormNetwork == null)
        _LoadNetwork(false);

      this.Toolbox.AccumulateRaster(accumulationLayer.Raster);
      StormDrainNetworkLoader loader = _CreateStormNetworkLoader();
      loader.FlowAccumulationField = validFieldName;
      loader.UpdateStormNetwork(conversionFactor);

      _LogAccumulateEnd(validFieldName);

      _ReportProgress(this, new ProgressEventArgs(1, 1, "Finished accumulating raster."));
    }

    private void _LogAccumulateStart(IRaster accumulationRaster, double conversionFactor)
    {
      Logger.Log("---------------------------------------");
      Logger.Log(string.Format("Accumulate Raster initiated by {0}", Environment.UserName));
      Logger.Log(string.Format("INPUTS:"));
      Logger.Log(string.Format("Raster to Accumulate: {0}", GeoprocessingUtility.GetRasterPath(accumulationRaster)));
      Logger.Log(string.Format("Storm Network Nodes: {0}", GeoprocessingUtility.GetFeatureClassPath(this.Toolbox.StormNetworkNodeClass)));
      Logger.Log(string.Format("Storm Network Conveyances: {0}", GeoprocessingUtility.GetFeatureClassPath(this.Toolbox.StormNetworkConveyanceClass)));
      Logger.Log(string.Format("Catchments: {0}", GeoprocessingUtility.GetFeatureClassPath(this.Toolbox.CatchmentClass)));
      Logger.Log(string.Format("Flow Direction Raster: {0}", GeoprocessingUtility.GetRasterPath(this.Toolbox.FlowDirRaster)));
      Logger.Log(string.Format("Conversion Factor: {0}", conversionFactor));
      Logger.Log("---------------------------------------");
    }

    private void _LogAccumulateEnd(string validFieldName)
    {
      Logger.Log("---------------------------------------");
      Logger.Log(string.Format("Accumulate Raster completed."));
      Logger.Log(string.Format("OUTPUTS:"));
      Logger.Log(string.Format("Conveyance Output Field: {0}", validFieldName));
      Logger.Log("---------------------------------------");
    }

    private void rasterComboBox_SelectionChangeCommitted(object sender, EventArgs e)
    {
      _SafeExecute(_UpdateRasterValue);
    }

    private void _UpdateRasterValue()
    {
      List<string> fieldNames = new List<string>();
      ITableFields rasterFields = rasterComboBox.SelectedValue as ITableFields;
      try
      {
        if (rasterFields != null)
        {
          for (int i = 0; i < rasterFields.FieldCount; i++)
          {
            fieldNames.Add(rasterFields.get_Field(i).Name);
          }
        }
      }
      catch 
      {
        fieldNames.Clear();
      }

      if (fieldNames.Count == 0)
      {
        fieldNames.Add("Value");
      }

      valueComboBox.Items.Clear();
      valueComboBox.Items.AddRange(fieldNames.ToArray());
      valueComboBox.SelectedItem = "Value";

      fieldPrefixTextBox.Text = rasterComboBox.Text;
    }

    private void receivingWatersComboBox_SelectionChangeCommitted(object sender, EventArgs e)
    {
      _SafeExecute(delegate
      {
        List<string> fieldNames = new List<string>();
        ITableFields watersFields = receivingWatersComboBox.SelectedValue as ITableFields;
        try
        {
          if (watersFields != null)
          {
            for (int i = 0; i < watersFields.FieldCount; i++)
            {
              IField field = watersFields.get_Field(i);
              if (field.Type != esriFieldType.esriFieldTypeGeometry)
                fieldNames.Add(field.Name);
            }
          }
        }
        catch
        {
          fieldNames.Clear();
        }

        receivingWatersAttributeComboBox.Items.Clear();
        receivingWatersAttributeComboBox.Items.AddRange(fieldNames.ToArray());
        if (fieldNames.Count > 0)
          receivingWatersAttributeComboBox.SelectedItem = fieldNames[0];
      });
    }

    private void receivingWatersAttributeComboBox_SelectionChangeCommitted(object sender, EventArgs e)
    {
      _SafeExecute(delegate
      {
        if (string.IsNullOrEmpty(newFieldNameTextBox.Text.Trim()) && receivingWatersAttributeComboBox.SelectedItem != null)
        {
          newFieldNameTextBox.Text = receivingWatersAttributeComboBox.SelectedItem.ToString();
        }
      });
    }

    private void transferAttributeButton_Click(object sender, EventArgs e)
    {
      _SafeExecute(_TransferAttribute);
    }

    private void _TransferAttribute()
    {
      IFeatureLayer watersLayer = receivingWatersComboBox.SelectedValue as IFeatureLayer;
      if (watersLayer == null || watersLayer.FeatureClass == null)
      {
        MessageBox.Show(this, "The receiving waters layer is not valid.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
      }

      string sourceField = receivingWatersAttributeComboBox.SelectedItem as string;
      if (sourceField == null)
      {
        MessageBox.Show(this, "The receiving waters attribute is not valid.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
      }

      string newFieldName = newFieldNameTextBox.Text.Trim();
      if (string.IsNullOrEmpty(newFieldName))
      {
        MessageBox.Show(this, "The new field name is not valid.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
      }

      IFeatureLayer conveyanceLayer = conveyanceComboBox.SelectedValue as IFeatureLayer;
      IFeatureLayer nodeLayer = nodesComboBox.SelectedValue as IFeatureLayer;
      if (conveyanceLayer == null || nodeLayer == null || conveyanceLayer.FeatureClass == null || nodeLayer.FeatureClass == null)
      {
        MessageBox.Show(this, "The selected storm network layers (nodes and conveyance) are not valid.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
      }

      string validFieldName = FeatureClassUtility.ValidateField(nodeLayer.FeatureClass, newFieldName);
      if (nodeLayer.FeatureClass.FindField(validFieldName) != -1 ||
        conveyanceLayer.FeatureClass.FindField(validFieldName) != -1)
      {
        DialogResult response = MessageBox.Show(this, "The storm network already contains output fields for the given attribute. Do you want to overwrite the values in these fields?", "Overwrite values?", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
        if (response == System.Windows.Forms.DialogResult.No)
          return;
      }

      _LogReceivingWatersStart(watersLayer, sourceField);

      if (this.Toolbox.StormNetwork == null)
        _LoadNetwork(false);

      this.Toolbox.TransferReceivingAttribute(watersLayer.FeatureClass, sourceField, ultimateWatersRadioButton.Checked);

      IField templateField = watersLayer.FeatureClass.Fields.get_Field(watersLayer.FeatureClass.FindField(sourceField));

      StormDrainNetworkLoader loader = _CreateStormNetworkLoader();
      if (loader.StormNetworkNodeClass.FindField(validFieldName) == -1)
      {
        IFeatureClass nodeClass = loader.StormNetworkNodeClass;
        FeatureClassUtility.AddField(ref nodeClass, validFieldName, templateField, true);
        loader.StormNetworkNodeClass = nodeClass;
      }
      if (loader.StormNetworkConveyanceClass.FindField(validFieldName) == -1)
      {
        IFeatureClass conveyanceClass = loader.StormNetworkConveyanceClass;
        FeatureClassUtility.AddField(ref conveyanceClass, validFieldName, templateField, true);
        loader.StormNetworkConveyanceClass = conveyanceClass;
      }
      loader.TagField = validFieldName;
      loader.UpdateStormNetwork();

      _LogReceivingWatersEnd(validFieldName);

      _ReportProgress(this, new ProgressEventArgs(1, 1, "Finished transfering receiving water attribute."));
    }

    private void _LogReceivingWatersStart(IFeatureLayer watersLayer, string watersAttribute)
    {
      Logger.Log("---------------------------------------");
      Logger.Log(string.Format("Transfer receiving waters attribute initiated by {0}", Environment.UserName));
      Logger.Log(string.Format("INPUTS:"));
      Logger.Log(string.Format("Storm Network Nodes: {0}", GeoprocessingUtility.GetFeatureClassPath(this.Toolbox.StormNetworkNodeClass)));
      Logger.Log(string.Format("Storm Network Conveyances: {0}", GeoprocessingUtility.GetFeatureClassPath(this.Toolbox.StormNetworkConveyanceClass)));
      Logger.Log(string.Format("Receiving Waters: {0}", GeoprocessingUtility.GetFeatureClassPath(watersLayer.FeatureClass)));
      Logger.Log(string.Format("Receiving Waters Attribute: {0}", watersAttribute));
      Logger.Log("---------------------------------------");
    }

    private void _LogReceivingWatersEnd(string validFieldName)
    {
      Logger.Log("---------------------------------------");
      Logger.Log(string.Format("Transfer receiving waters attribute completed."));
      Logger.Log(string.Format("OUTPUTS:"));
      Logger.Log(string.Format("Output Field: {0}", validFieldName));
      Logger.Log("---------------------------------------");
    }

    private void intersectButton_Click(object sender, EventArgs e)
    {
      _SafeExecute(_IntersectRaster);
    }

    private void _IntersectRaster()
    {
      IRasterLayer rasterLayer = rasterComboBox.SelectedValue as IRasterLayer;
      if (rasterLayer != null && rasterLayer.Raster != null)
      {
        PolylineRasterIntersect intersector = new PolylineRasterIntersect();
        intersector.Progress += new EventHandler<ProgressEventArgs>(_ReportProgress);
        intersector.CategoryRaster = rasterLayer.Raster;
        intersector.RasterField = valueComboBox.SelectedItem as string;
        intersector.PolylineClass = this.Toolbox.StormNetworkConveyanceClass;
        intersector.PolylineIdField = "Id";
        intersector.OutputFieldPrefix = fieldPrefixTextBox.Text;

        intersector.Intersect();

        ////IStandaloneTable standAloneTable = new StandaloneTableClass();
        ////standAloneTable.Table = summaryTable;

        ////IStandaloneTableCollection mapTables = ArcMap.Document.FocusMap as IStandaloneTableCollection;
        ////mapTables.AddStandaloneTable(standAloneTable);
        ////ArcMap.Document.UpdateContents();

        ////ITableWindow tableWindow = new TableWindowClass();
        ////tableWindow.Application = ArcMap.Application;
        ////tableWindow.Table = standAloneTable.Table;
        ////tableWindow.Show(true);
      }
    }

    private void eidButton_Click(object sender, EventArgs e)
    {
      _SafeExecute(this.Toolbox.UpdateEids);
    }

    private void markEndsButton_Click(object sender, EventArgs e)
    {
      _SafeExecute(this.Toolbox.MarkDownstreamEnds);
    }

    private void findFlippedDmeButton_Click(object sender, EventArgs e)
    {
      _SafeExecute(this.Toolbox.FindFlippedInletLeads);
    }

    private void _UpdateToolBox()
    {
      IGeometricNetwork geometricNetwork = networkComboBox.SelectedValue as IGeometricNetwork;
      IFeatureLayer catchmentLayer = catchmentComboBox.SelectedValue as IFeatureLayer;
      IRasterLayer flowDirLayer = flowDirComboBox.SelectedValue as IRasterLayer;
      IRasterLayer runoffLayer = runoffComboBox.SelectedValue as IRasterLayer;
      IFeatureLayer nodesLayer = nodesComboBox.SelectedValue as IFeatureLayer;
      IFeatureLayer conveyanceLayer = conveyanceComboBox.SelectedValue as IFeatureLayer;

      this.Toolbox.GeometricNetwork = geometricNetwork;
      this.Toolbox.CatchmentClass = (catchmentLayer == null ? null : catchmentLayer.FeatureClass);
      this.Toolbox.FlowDirRaster = (flowDirLayer == null ? null : flowDirLayer.Raster);
      this.Toolbox.RunoffRaster = (runoffLayer == null ? null : runoffLayer.Raster);
      this.Toolbox.StormNetworkNodeClass = (nodesLayer == null ? null : nodesLayer.FeatureClass);
      this.Toolbox.StormNetworkConveyanceClass = (conveyanceLayer == null ? null : conveyanceLayer.FeatureClass);
      this.Toolbox.ExtractTerminalFlow = includeTermFlowCheckBox.Checked;
    }

    private void _UpdateStormNetworkLabel()
    {
      if (this.Toolbox.StormNetwork != null)
      {
        int orphans = 0;
        int loops = 0;
        int diversions = 0;
        foreach (NodePoint node in this.Toolbox.StormNetwork.Nodes)
        {
          if (node.IsOrphan)
            orphans++;
          if (node.IsInLoop)
            loops++;
          if (node.HasDivergentFlow)
            diversions++;
        }

        string nl = Environment.NewLine;
        string line1 = "Storm Network: Loaded";
        string line2 = string.Format("Nodes: {0} Inlets: {1}  Outlets: {2}", this.Toolbox.StormNetwork.Nodes.Count, this.Toolbox.StormNetwork.Inlets.Count, this.Toolbox.StormNetwork.Outlets.Count);
        string line3 = string.Format("Catchments: {0}  Flowlines: {1} Conveyances: {2}", this.Toolbox.StormNetwork.Catchments.Count, this.Toolbox.StormNetwork.FlowLines.Count, this.Toolbox.StormNetwork.Conveyances.Count);
        string line4 = string.Format("Orphans: {0}  Loops: {1}  Diversions: {2}", orphans, loops, diversions);

        stormNetworkLabel.Text = line1 + nl + line2 + nl + line3 + nl + line4;
        Logger.Log("---------------------------------------");
        Logger.Log(line1);
        Logger.Log(line2);
        Logger.Log(line3);
        Logger.Log(line4);
        Logger.Log("---------------------------------------");
      }
      else
      {
        stormNetworkLabel.Text = "Storm Network: Not Loaded";
      }
    }

    private void _ShowError(Exception ex)
    {
      string msg = ex.GetType().FullName + ": " + ex.Message;
      msg += Environment.NewLine + ex.StackTrace;
      MessageBox.Show(this, msg, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

    private void _ReportProgress(object sender, ProgressEventArgs e)
    {
      progressBar.Minimum = 0;
      progressBar.Maximum = e.Total;
      progressBar.Value = e.Progress;
      statusLabel.Text = e.Message;

      if (e.Progress == 0 || (e.Progress == 1 && e.Total == 1))
        progressLabel.Text = "";
      else
        progressLabel.Text = string.Format("{0}/{1}", e.Progress, e.Total);

      cancelButton.Enabled = e.CanCancel;

      Application.DoEvents();

      if (_cancelPressed)
      {
        e.Cancel = true;
        _cancelPressed = false;
      }
    }

    private void cancelButton_Click(object sender, EventArgs e)
    {
      try
      {
        _cancelPressed = true;
      }
      catch (Exception ex)
      {
        _ShowError(ex);
      }
    }

    private IFeatureLayer _FindFeatureLayer(IFeatureClass featureClass)
    {
      if (featureClass == null)
        return null;

      foreach (ILayer layer in _GetLayers())
      {
        IFeatureLayer featureLayer = layer as IFeatureLayer;
        if (featureLayer != null && featureLayer.FeatureClass != null)
        {
          if (featureLayer.FeatureClass == featureClass)
            return featureLayer;
        }
      }
      return null;
    }

    private IRasterLayer _FindRasterLayer(IRaster raster)
    {
      if (raster == null)
        return null;

      foreach (ILayer layer in _GetLayers())
      {
        IRasterLayer rasterLayer = layer as IRasterLayer;
        if (rasterLayer != null && rasterLayer.Raster != null)
        {
          // Since there can be multiple instances of the raster created from a raster dataset
          // we check for equivalency by looking at the "complete name" of each raster's underlying
          // dataset. Note that the rasters could have different properties.

          string layerPath = ((IRasterAnalysisProps)rasterLayer.Raster).RasterDataset.CompleteName;
          string rasterPath = ((IRasterAnalysisProps)raster).RasterDataset.CompleteName;
          if (string.Compare(layerPath, rasterPath, true) == 0)
            return rasterLayer;
        }
      }
      return null;
    }

    private List<ILayer> _GetLayers()
    {
      List<ILayer> layers = new List<ILayer>();

      IMap map = null;
      try
      {
        map = ArcMap.Document.FocusMap;
        for (int i = 0; i < map.LayerCount; i++)
        {
          ILayer pLayer = map.get_Layer(i);
          if (pLayer is ICompositeLayer)
            _AddSubLayers((ICompositeLayer)pLayer, layers);
          else
            layers.Add(pLayer);
        }
      }
      finally
      {
        ComUtility.ReleaseComObject(map);
      }

      return layers;
    }

    private void _AddSubLayers(ICompositeLayer compositeLayer, List<ILayer> layers)
    {
      for (int i = 0; i < compositeLayer.Count; i++)
      {
        ILayer pLayer = compositeLayer.get_Layer(i);
        if (pLayer is ICompositeLayer)
          _AddSubLayers((ICompositeLayer)pLayer, layers);
        else
          layers.Add(pLayer);
      }
    }

    private void accumulationComboBox_SelectionChangeCommitted(object sender, EventArgs e)
    {
      IRasterLayer accumulationLayer = accumulationComboBox.SelectedValue as IRasterLayer;
      if (accumulationLayer == null)
      {
        return;
      }

      accumulationRasterFieldTextBox.Text = accumulationLayer.Name;
    }

    protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
    {
      _SafeExecute(_UpdateSettings);
    }

    private void loggingCheckBox_CheckedChanged(object sender, EventArgs e)
    {
      _SafeExecute(delegate
      {
        if (loggingCheckBox.Checked)
          Logger.StartLogging();
        else
          Logger.StopLogging();
      });
    }

    private void testButton_Click(object sender, EventArgs e)
    {
      _SafeExecute(delegate
      {
        if (Environment.UserName.Contains("jburns"))
          executeJohnsTest();
        else if (Environment.UserName.Contains("aengelmann"))
          executeArnoldsTest();
      });
    }

    private void executeJohnsTest()
    {
      try
      {

      }
      catch (Exception ex)
      {
      }
    }

    private void executeArnoldsTest()
    {
      string documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
      string tempDir = System.IO.Path.Combine(documents, "temp");
      string scriptPath = System.IO.Path.Combine(tempDir, "CalculateTotalAccumulation.py");
      System.IO.Directory.CreateDirectory(tempDir);

      System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
      using (System.IO.StreamReader templateReader = new System.IO.StreamReader(assembly.GetManifestResourceStream("Bes.Swsp.ToolBox.Templates.CalculateTotalAccumulation.py")))
      {
        using (System.IO.StreamWriter scriptWriter = new System.IO.StreamWriter(scriptPath))
        {
          while (templateReader.Peek() != -1)
          {
            scriptWriter.WriteLine(templateReader.ReadLine());
          }
        }
      }

      System.Diagnostics.Process python = null;
      bool restartLog = false;
      if (Logger.IsLogging)
      {
        // Release log file to allow logging by script
        Logger.StopLogging();
        restartLog = true;
      }
      try
      {
        string pythonPath = string.Empty;
        string envPath = Environment.GetEnvironmentVariable("PYTHONPATH");
        if (envPath == null)
          envPath = string.Empty;

        if (System.IO.File.Exists(@"C:\python27\ArcGISx6410.2\python.exe"))
        {
          pythonPath = @"C:\python27\ArcGISx6410.2\python.exe";
          Environment.SetEnvironmentVariable("PYTHONPATH", null);
        }
        else if (System.IO.File.Exists(@"C:\python27\ArcGIS10.2\python.exe"))
        {
          pythonPath = @"C:\python27\ArcGIS10.2\python.exe";
          Environment.SetEnvironmentVariable("PYTHONPATH", null);
        }

        if (!string.IsNullOrEmpty(pythonPath))
        {
          python = System.Diagnostics.Process.Start(pythonPath, scriptPath);
        }
        else
        {
          python = System.Diagnostics.Process.Start(scriptPath);
        }

        while (!python.HasExited)
        {
          System.Threading.Thread.Sleep(500);
        }

        if (System.IO.File.Exists(scriptPath))
          System.IO.File.Delete(scriptPath);
      }
      finally
      {
        if (restartLog)
          Logger.StartLogging();
      }

    }
  }
}
